package com.tzm.gaflow.web.exception;

import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.tzm.gaflow.web.domain.ErrorDetail;
import com.tzm.gaflow.web.domain.ErrorStack;
import com.tzm.gaflow.web.domain.GlobalError;
import com.tzm.gaflow.web.domain.Result;
import com.tzm.gaflow.web.util.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * 通用异常处理类
 *
 */
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 400 - 访问类型不被允许
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<Result> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        e.printStackTrace();
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.METHOD_NOT_ALLOWED, null, errorStack);
        return new ResponseEntity(result, HttpStatus.METHOD_NOT_ALLOWED);
    }

    /**
     * 423 - json解析参数错误
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<Result> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        e.printStackTrace();
        Throwable throwable = e.getCause();
        List<ErrorDetail> errorDetails = new ArrayList<>();
        if (InvalidFormatException.class.isInstance(throwable)) {
            InvalidFormatException invalidFormatException = (InvalidFormatException) throwable;
            errorDetails.add(new ErrorDetail(invalidFormatException.getPath().get(0).getFieldName(), invalidFormatException.getValue(), invalidFormatException.getOriginalMessage()));
        }

        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.METHOD_NOT_ALLOWED, errorDetails, errorStack);
        return new ResponseEntity(result, HttpStatus.METHOD_NOT_ALLOWED);

    }

    /**
     * 423 - 参数验证类型错误
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<Result> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        e.printStackTrace();
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.UNPROCESABLE_ENTITY, null, errorStack);
        return new ResponseEntity(result, HttpStatus.UNPROCESSABLE_ENTITY);
    }

    /**
     * 423 - 参数验证类型错误
     */
    @ExceptionHandler(NumberFormatException.class)
    public ResponseEntity<Result> handleNumberFormatException(NumberFormatException e) {
        e.printStackTrace();
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.UNPROCESABLE_ENTITY, null, errorStack);
        return new ResponseEntity(result, HttpStatus.UNPROCESSABLE_ENTITY);
    }

    /**
     * 423 - 参数验证类型错误
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Result> handleException(IllegalArgumentException e) {
        e.printStackTrace();
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.UNPROCESABLE_ENTITY, null, errorStack);
        return new ResponseEntity(result, HttpStatus.UNPROCESSABLE_ENTITY);
    }

    /**
     * 423 框架参数验证错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Result> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        e.printStackTrace();
        List<ErrorDetail> errorDetails = new ArrayList<>();
        for (FieldError error : e.getBindingResult().getFieldErrors()) {
            errorDetails.add(new ErrorDetail(error.getField(), error.getRejectedValue(), error.getDefaultMessage()));
        }
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.UNPROCESABLE_ENTITY, errorDetails, errorStack);
        return new ResponseEntity(result, HttpStatus.UNPROCESSABLE_ENTITY);
    }

    /**
     * 业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Result> handleServiceException(BusinessException e) {
        e.printStackTrace();
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(e.getCode(), e.getMessage(), e.getErrorType(), e.getErrorDetails(), errorStack);
        return new ResponseEntity(result, HttpStatus.OK);
    }

    /**
     * 500 - 未知异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Result> handleException(Exception e) {
        e.printStackTrace();
        ErrorStack errorStack = new ErrorStack(e.getMessage(), getStackTrace(e));
        Result result = ResultUtils.fail(GlobalError.INTERNAL_SERVER_ERROR, null, errorStack);
        return new ResponseEntity(result, HttpStatus.OK);
    }

    /**
     * 获得堆栈信息
     *
     * @param throwable
     * @return
     */
    public static String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try {
            throwable.printStackTrace(pw);
            return sw.toString();
        } finally {
            pw.close();
        }
    }
}